//
//  TableCache.cpp
//
//  Created by shunguo.chen chen on 7/31/17.
//

#include "TableCache.h"
#include "cocos2d.h"
#include "CSVparser.hpp"

extern "C" {
#include "lua.h"
#include "tolua++.h"
#include "sqlite3.h"
}
#include "tolua_fix.h"
#include "LuaBasicConversions.h"
#include "extend/FileDecoder.h"

using namespace std;
using namespace csv;
USING_NS_CC;

//------------------ TableCache -------------------//
static TableCache* s_tableCache = nullptr;

TableCache* TableCache::getInstance()
{
    if (! s_tableCache)
    {
        s_tableCache = new (std::nothrow) TableCache();
    }
    
    return s_tableCache;
}

TableCache::TableCache()
{
}

TableCache::~TableCache()
{
    removeAllCache();
}

Parser* TableCache::createTable(const string& tabKey)
{
    string name = "res/data/";
    name.append(tabKey);
    name.append(".csv");
    // FileUtils::getInstance()->getContents(name, &content);
    Data data = FileUtils::getInstance()->getDataFromFile(name);
    unsigned long size = data.getSize();
    unsigned char* buf = data.getBytes();
    
    Data outData;
    if(FileDecoder::decode(data,outData))
    {
        size = outData.getSize();
        buf = outData.getBytes();
    }
    string content((char *)buf, size);
    
    if(content == "")
    {
        CCLOG("TableCache GameDB createTable = %s fail", tabKey.c_str());
        return nullptr;
    }
    else
    {
        Parser* table = new Parser(content, DataType::ePURE);
        // CCLOG("TableCache GameDB createTable = %s, rows = %d", tabKey.c_str(), table->rowCount());
        return table;
    }
}

void TableCache::addCache(const string& tabKey, Parser* table)
{
    removeCache(tabKey);
    if(table != nullptr)
    {
        _tables.emplace(tabKey, table);
    }
}

Parser* TableCache::getCache(const string& tabKey)
{
    auto iter = _tables.find(tabKey);
    if(iter != _tables.end())
    {
        return iter->second;
    }
    else
    {
        Parser* table = createTable(tabKey);
        addCache(tabKey, table);
        return table;
    }
}

void TableCache::removeCache(const string& key)
{
    auto it = _tables.find(key);
    if(it != _tables.end())
    {
        Parser* table = it->second;
        delete table;
        _tables.erase(it);
    }
}

void TableCache::removeAllCache()
{
    for (auto iter = _tables.begin(); iter != _tables.end(); ++iter)
    {
        Parser* table = iter->second;
        delete table;
    }
    _tables.clear();
}

string TableCache::getCacheValue(const string& tabKey, const string& rowKey, const string& paramKey)
{
    string value = "";
    Parser* table = getCache(tabKey);
    if(table != nullptr)
    {
        Row* row = table->getRow(rowKey);
        if(row)
        {
            value = (*row)[paramKey];
        }
    }
    return value;
}

string TableCache::getCacheValue(const string& tabKey, const string& colKey, const string& colVal, const string& paramKey)
{
    string value = "";
    Parser* table = getCache(tabKey);
    if(table != nullptr)
    {
        const RowDict& rows = table->getRows();
        for (auto iter = rows.begin(); iter != rows.end(); ++iter)
        {
            Row* row = iter->second;
            if((*row)[colKey] == colVal)
            {
                value = (*row)[paramKey];
                break;
            }
        }
    }
    return value;
}

//------------------ regist lua -------------------//
int lua_TableCache_getCacheValue(lua_State* L)
{
    assert(lua_gettop(L)==2);

    const char* tabKey = tolua_tostring(L,1,0);

    TableCache* ret = TableCache::getInstance();
    Parser* table = ret->getCache(tabKey);

    if(table)
    {
        if(lua_isfunction(L, 2))
        {
            const RowDict& rows = table->getRows();
            bool found = false;
            for (auto iter = rows.begin(); iter != rows.end(); ++iter)
            {
                lua_pushvalue(L, 2);
                tolua_pushusertype(L,(void*)iter->second,"Row");
                lua_call(L, 1, 1);
                
                found = tolua_toboolean(L, -1, false);
                lua_pop(L, 1);
                if(found)
                {
                    tolua_pushusertype(L,(void*)iter->second,"Row");
                    break;
                }
            }
            if(!found)
                lua_pushnil(L);
        }
        else if(lua_isstring(L, 2))
        {
            const char* rowKey = tolua_tostring(L,2,0);
            Row* row = table->getRow(rowKey);
            tolua_pushusertype(L,(void*)row,"Row");
        }
        else
        {
            lua_pushnil(L);
        }
    }
    else
    {
        lua_pushnil(L);
    }
    return 1;
}

int lua_TableCache_getCacheValues(lua_State* L)
{
    const char* tabKey = tolua_tostring(L,1,0);

    TableCache* ret = TableCache::getInstance();
    Parser* table = ret->getCache(tabKey);

    if(table)
    {
        const RowDict& rows = table->getRows();
        int max = lua_gettop(L);
        int idx = 0;
        if(max == 2 && lua_isfunction(L, 2))
        {
            lua_newtable(L);
            for (auto iter = rows.begin(); iter != rows.end(); ++iter)
            {
                lua_pushvalue(L, 2);
                tolua_pushusertype(L,(void*)iter->second,"Row");
                lua_call(L, 1, 1);
                
                bool isOk = tolua_toboolean(L, -1, false);
                lua_pop(L, 1);
                if(isOk)
                {
                    idx++;
                    lua_pushnumber(L, idx);
                    tolua_pushusertype(L,(void*)iter->second,"Row");
                    lua_rawset(L, -3);
                }
            }
        }
        else
        {
            lua_newtable(L);
            for (auto iter = rows.begin(); iter != rows.end(); ++iter)
            {   
                idx++;
                lua_pushnumber(L, idx);
                tolua_pushusertype(L,(void*)iter->second,"Row");
                lua_rawset(L, -3);
            }
        }
    }
    else
    {
        lua_pushnil(L);
    }
    return 1;
}

int lua_TableCache_getCache(lua_State* L)
{
    assert(lua_gettop(L)==1);

    const char* tabKey = tolua_tostring(L,1,0);

    TableCache* ret = TableCache::getInstance();
    Parser* table = ret->getCache(tabKey);
    // object_to_luaval<Parser>(L, "Parser", table);
    if(table)
    {
        const RowDict& rows = table->getRows();
        lua_newtable(L);
        for (auto iter = rows.begin(); iter != rows.end(); ++iter)
        {
            lua_pushstring(L, iter->first.c_str());
            tolua_pushusertype(L,(void*)iter->second,"Row");
            lua_rawset(L, -3);
        }
    }
    else
    {
        lua_pushnil(L);
    }
    return 1;
}

int lua_TableCache_addCache(lua_State* L)
{
    int argc = lua_gettop(L);

    if(argc == 2)
    {
        const char* tabKey = tolua_tostring(L,1,0);
        Parser* table = (Parser*)tolua_tousertype(L,2,0);
        if(table)
        {
            TableCache* ret = TableCache::getInstance();
            ret->addCache(tabKey, table);
        }
    }
    else
    {
        const char* tabKey = tolua_tostring(L,1,0);
        TableCache* ret = TableCache::getInstance();
        Parser* table = ret->createTable(tabKey);
        ret->addCache(tabKey, table);
    }
    
    return 0;
}

int lua_TableCache_removeCache(lua_State* L)
{
    assert(lua_gettop(L)==1);

    const char* tabKey = tolua_tostring(L,1,0);

    TableCache* ret = TableCache::getInstance();
    ret->removeCache(tabKey);
    return 0;
}

int lua_TableCache_removeAllCache(lua_State* L)
{
    TableCache* ret = TableCache::getInstance();
    ret->removeAllCache();
    return 0;
}

int lua_Table_getRow(lua_State* L)
{
    assert(lua_gettop(L)==2);
    Parser* table = (Parser*)tolua_tousertype(L,1,0);
    assert(table);

    const char* key = tolua_tostring(L,2,0);
    Row* row = table->getRow(key);
    object_to_luaval<Row>(L, "Row", row);
    return 1;
}

int lua_Row_getValue(lua_State* L)
{
    assert(lua_gettop(L)==2);
    Row* row = (Row*)tolua_tousertype(L,1,0);
    assert(row);

    const char* key = tolua_tostring(L,2,0);
    const std::string& value = (*row)[key];
    if(!value.empty())
        tolua_pushstring(L, value.c_str());
    else
        lua_pushnil(L);
    return 1;
}

int lua_Row_getValues(lua_State* L)
{
    assert(lua_gettop(L)==1);
    Row* row = (Row*)tolua_tousertype(L,1,0);
    assert(row);
    
    const StringList& keys = row->getHeader();
    lua_newtable(L);
    
    for(int i = 0; i < keys.size(); i++)
    {
        const std::string& key = keys[i];
        const std::string& value = (*row)[i];
        if(!value.empty())
        {
            lua_pushstring(L, key.c_str());
            lua_pushstring(L, value.c_str());
            lua_rawset(L, -3);
        }
    }
    return 1;
}

int lua_Row_dump(lua_State* L)
{
    assert(lua_gettop(L)>=1);
    Row* row = (Row*)tolua_tousertype(L,1,0);
    assert(row);

    if(lua_gettop(L)==2)
    {
        const char* info = tolua_tostring(L,2,0);
        row->dump(info ? info : "");
    }
    else
    {
        row->dump("");
    }
    return 0;
}

int TableCache::register2lua(lua_State* L)
{
    tolua_open(L);
    tolua_module(L,"cc",0);
    tolua_beginmodule(L,"cc");

    // register TableCache
    tolua_usertype(L,"TableCache");
    tolua_cclass(L,"TableCache","TableCache","",nullptr);
    tolua_beginmodule(L,"TableCache");
    tolua_function(L,"getCacheValue", lua_TableCache_getCacheValue);
    tolua_function(L,"getCacheValues", lua_TableCache_getCacheValues);
    tolua_function(L,"getCache", lua_TableCache_getCache);
    tolua_function(L,"addCache", lua_TableCache_addCache);
    tolua_function(L,"removeCache", lua_TableCache_removeCache);
    tolua_function(L,"removeAllCache", lua_TableCache_removeAllCache);
    tolua_endmodule(L);


    // register Row
    tolua_usertype(L,"Row");
    tolua_cclass(L,"Row","Row","",nullptr);
    tolua_beginmodule(L,"Row");
    tolua_function(L,"getValue", lua_Row_getValue);
    tolua_function(L,"getValues", lua_Row_getValues);
    tolua_function(L,"dump", lua_Row_dump);
    tolua_function(L,".get",lua_Row_getValue);
    tolua_endmodule(L);

    tolua_endmodule(L);
    return 0;
}
